package com.fanqiechaodan.redis.service.impl;

import com.alibaba.fastjson.JSON;
import com.fanqiechaodan.redis.entity.User;
import com.fanqiechaodan.redis.mapper.UserMapper;
import com.fanqiechaodan.redis.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author fanqiechaodan
 * @Classname UserServiceImpl
 * @Description
 */
@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper userMapper;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    /**
     * 储存用户信息的key前缀
     */
    private static final String REDIS_USER_PREFIX = "user_";

    /**
     * redis加锁前缀
     */
    private static final String REDIS_LOCK_PREFIX = "lock_";

    @Override
    public User getUser(String id) {
        String resStr = stringRedisTemplate.opsForValue().get(REDIS_USER_PREFIX + id);
        if (StringUtils.isNotBlank(resStr)) {
            log.info("从redis获取返回...");
            return JSON.parseObject(resStr, User.class);
        }
        log.info("从mysql获取...");
        User res = userMapper.getById(id);
        if (Objects.nonNull(res)) {
            log.info("从mysql获取到的不为null设置过期时间(5-10之间的随机数)");
            long timeout = (int) (Math.random() * 5 + 5);
            stringRedisTemplate.opsForValue().set(REDIS_USER_PREFIX + id, JSON.toJSONString(res), timeout, TimeUnit.MINUTES);
        }
        return res;
    }

    /**
     * 缓存击穿
     *
     * @param id
     * @return
     */
    private User breakdown(String id) {
        String resStr = stringRedisTemplate.opsForValue().get(REDIS_USER_PREFIX + id);
        if (StringUtils.isNotBlank(resStr)) {
            log.info("从redis获取返回...");
            return JSON.parseObject(resStr, User.class);
        }
        // 使用redis进行加锁
        Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_PREFIX + id, "1", 10, TimeUnit.SECONDS);
        User res = null;
        try {
            if (lockFlag) {
                log.info("加锁成功,重新构建缓存...");
                res = userMapper.getById(id);
                stringRedisTemplate.opsForValue().set(REDIS_USER_PREFIX + id, JSON.toJSONString(res), 5, TimeUnit.MINUTES);
            } else {
                log.info("加锁失败,休眠进行重试...");
                Thread.sleep(50);
                getUser(id);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            if (lockFlag) {
                stringRedisTemplate.delete(REDIS_LOCK_PREFIX + id);
            }
        }
        return res;
    }

    /**
     * 缓存雪崩
     *
     * @param id
     * @return
     */
    private User avalanche(String id) {
        String resStr = stringRedisTemplate.opsForValue().get(REDIS_USER_PREFIX + id);
        if (StringUtils.isNotBlank(resStr)) {
            log.info("从redis获取返回...");
            return JSON.parseObject(resStr, User.class);
        }
        log.info("从mysql获取...");
        User res = userMapper.getById(id);
        if (Objects.nonNull(res)) {
            log.info("从mysql获取到的不为null设置过期时间(5-10之间的随机数)");
            long timeout = (int) (Math.random() * 5 + 5);
            stringRedisTemplate.opsForValue().set(REDIS_USER_PREFIX + id, JSON.toJSONString(res), timeout, TimeUnit.MINUTES);
        }
        return res;
    }

    /**
     * 缓存穿透
     *
     * @param id
     * @return
     */
    private User penetrate(String id) {
        String resStr = stringRedisTemplate.opsForValue().get(REDIS_USER_PREFIX + id);
        if (StringUtils.isNotBlank(resStr)) {
            log.info("从redis获取返回...");
            return JSON.parseObject(resStr, User.class);
        }
        if (StringUtils.EMPTY.equals(resStr)) {
            log.info("缓存虽然命中,但是为StringUtils.EMPTY,直接返回null");
            return null;
        }
        log.info("从mysql获取...");
        User res = userMapper.getById(id);
        if (Objects.isNull(res)) {
            log.info("从mysql获取到的仍然为null,缓存空对象,设置较短过期时间");
            stringRedisTemplate.opsForValue().set(REDIS_USER_PREFIX + id, StringUtils.EMPTY, 60, TimeUnit.SECONDS);
            return res;
        }
        log.info("从mysql获取到的不为null设置较长的过期时间");
        stringRedisTemplate.opsForValue().set(REDIS_USER_PREFIX + id, JSON.toJSONString(res), 5, TimeUnit.MINUTES);
        return res;
    }

    @Override
    public List<String> getAllUserId() {
        return userMapper.getAllUserId();
    }

}
